package zap

import (
	"fmt"
	"gitee.com/dennis-kk/service-box-go/util/slog"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

type (
	zapLogger struct {
		// custom zap config
		cfg    *zapcore.EncoderConfig
		logger *zap.Logger
		opts   *Options
	}
)

// NewLogger builds a new logger based on options
func NewLogger(opts ...slog.Option) (slog.BoxLogger, error) {
	// Default options
	options := &Options{
		&slog.Options{
			LevelStr:        "info",
			LogLevel:        slog.LogInfo,
			Outs:            []string{"console"},
			TimeFormat:      "2006-01-02-15:04:05.000",
			Style:           "console",
			CallerSkipCount: 1,
		},
		zapcore.InfoLevel,
	}

	l := &zapLogger{opts: options}
	if err := l.Init(opts...); err != nil {
		return nil, err
	}
	return l, nil
}

func (lg *zapLogger) Init(options ...slog.Option) error {
	//var err error
	for _, o := range options {
		o(lg.opts.Options)
	}

	//preprocess
	lg.opts.preprocess()
	//construct zap
	lg.cfg = &zapcore.EncoderConfig{
		TimeKey:          "time",
		LevelKey:         "level",
		NameKey:          "logger",
		CallerKey:        "caller",
		MessageKey:       "message",
		StacktraceKey:    "stacktrace",
		LineEnding:       zapcore.DefaultLineEnding,
		EncodeLevel:      CapitalLevelEncoder, //自定义等级格式
		EncodeTime:       lg.opts.zapTimeFormatter,
		EncodeCaller:     ShortEncodeCaller,
		EncodeName:       zapcore.FullNameEncoder,
		ConsoleSeparator: " ",
	}

	//prepare encoder
	var enc zapcore.Encoder
	switch lg.opts.Style {
	case "json":
		enc = zapcore.NewJSONEncoder(*lg.cfg)
	case "console":
		enc = zapcore.NewConsoleEncoder(*lg.cfg)
	default:
		return fmt.Errorf("unsupported output style %s. must by [json, console] \n", lg.opts.Style)
	}

	//prepare options
	zapOpts := []zap.Option{
		zap.AddCaller(),
		zap.AddCallerSkip(lg.opts.CallerSkipCount),
	}

	if lg.opts.zapTrace > zapcore.InfoLevel {
		zapOpts = append(zapOpts, zap.AddStacktrace(lg.opts.zapTrace))
	}

	var cores []zapcore.Core
	for _, out := range lg.opts.Outs {
		switch out {
		case "console":
			core := zapcore.NewCore(enc, zapcore.AddSync(os.Stdout), zap.LevelEnablerFunc(lg.opts.zapEnableLevel))
			cores = append(cores, core)
		case "file":
			fwriter, err := lg.opts.zapTimeRotateWriter()
			if err != nil {
				return err
			}
			core := zapcore.NewCore(enc, fwriter, zap.LevelEnablerFunc(lg.opts.zapEnableLevel))
			cores = append(cores, core)
		default:
			return fmt.Errorf("unsupported log out writer %s \n", out)
		}
	}

	lg.logger = zap.New(zapcore.NewTee(cores...), zapOpts...)
	// add application name for box logger !
	if len(lg.opts.AppName) > 0 {
		lg.logger = lg.logger.Named(lg.opts.AppName)
	}
	return nil
}

func (lg *zapLogger) Options() *slog.Options {
	return lg.opts.Options
}

func (lg *zapLogger) Named(name string) slog.BoxLogger {
	return &zapLogger{
		cfg:    lg.cfg,
		opts:   lg.opts,
		logger: lg.logger.Named(name),
	}
}

func (lg *zapLogger) Children(opts ...slog.Option) slog.BoxLogger {
	option := &slog.Options{
		AppName: lg.opts.AppName,
	}

	for _, opt := range opts {
		opt(option)
	}

	clone := lg.logger.Named(option.AppName)
	// 暂时只支持名字 和 日志堆栈层级
	if option.CallerSkipCount > 0 {
		clone = lg.logger.WithOptions(zap.AddCallerSkip(option.CallerSkipCount))
	}

	return &zapLogger{
		cfg:    lg.cfg,
		opts:   lg.opts,
		logger: clone,
	}
}

func (lg *zapLogger) Log(level slog.Level, args ...interface{}) {
	lvl := loggerToZapLevel(level)

	if !lg.opts.zapEnableLevel(lvl) {
		return
	}

	msg := fmt.Sprint(args...)
	if ce := lg.logger.Check(lvl, msg); ce != nil {
		ce.Write()
	}
}

func (lg *zapLogger) Logf(level slog.Level, format string, args ...interface{}) {
	lvl := loggerToZapLevel(level)
	if !lg.opts.zapEnableLevel(lvl) {
		return
	}
	msg := lg.getMessage(format, args)
	if ce := lg.logger.Check(lvl, msg); ce != nil {
		ce.Write()
	}
}

func (lg *zapLogger) getMessage(format string, args []interface{}) string {
	if len(args) == 0 {
		return format
	}

	if format != "" {
		return fmt.Sprintf(format, args...)
	}

	if len(args) == 1 {
		if str, ok := args[0].(string); ok {
			return str
		}
	}

	return fmt.Sprint(args...)
}

func (lg *zapLogger) Debug(format string, args ...interface{}) {
	if !lg.opts.zapEnableLevel(zap.DebugLevel) {
		return
	}
	msg := lg.getMessage(format, args)
	if ce := lg.logger.Check(zap.DebugLevel, msg); ce != nil {
		ce.Write()
	}
}

func (lg *zapLogger) Info(format string, args ...interface{}) {
	if !lg.opts.zapEnableLevel(zap.InfoLevel) {
		return
	}
	msg := lg.getMessage(format, args)
	if ce := lg.logger.Check(zap.InfoLevel, msg); ce != nil {
		ce.Write()
	}
}

func (lg *zapLogger) Warn(format string, args ...interface{}) {
	if !lg.opts.zapEnableLevel(zap.WarnLevel) {
		return
	}
	msg := lg.getMessage(format, args)
	if ce := lg.logger.Check(zap.WarnLevel, msg); ce != nil {
		ce.Write()
	}
}

func (lg *zapLogger) Error(format string, args ...interface{}) {
	if !lg.opts.zapEnableLevel(zap.ErrorLevel) {
		return
	}
	msg := lg.getMessage(format, args)
	if ce := lg.logger.Check(zap.ErrorLevel, msg); ce != nil {
		ce.Write()
	}
}

func (lg *zapLogger) Fatal(format string, args ...interface{}) {
	if !lg.opts.zapEnableLevel(zap.FatalLevel) {
		return
	}
	msg := lg.getMessage(format, args)
	if ce := lg.logger.Check(zap.FatalLevel, msg); ce != nil {
		ce.Write()
	}
}

func loggerToZapLevel(level slog.Level) zapcore.Level {
	switch level {
	case slog.LogDebug:
		return zap.DebugLevel
	case slog.LogInfo:
		return zap.InfoLevel
	case slog.LogWarn:
		return zap.WarnLevel
	case slog.LogError:
		return zap.ErrorLevel
	case slog.LogFatal:
		return zap.FatalLevel
	default:
		return zap.InfoLevel
	}
}

func zapToLoggerLevel(level zapcore.Level) slog.Level {
	switch level {
	case zap.DebugLevel:
		return slog.LogDebug
	case zap.InfoLevel:
		return slog.LogInfo
	case zap.WarnLevel:
		return slog.LogWarn
	case zap.ErrorLevel:
		return slog.LogError
	case zap.FatalLevel:
		return slog.LogFatal
	default:
		return slog.LogInfo
	}
}
